
#include "shader.hpp"

#include <vector>
#include <string>

#include "api.hpp"
#include "utils.hpp"

namespace zen {

	namespace gl {

		std::vector<std::string> shaderDirectories;

		void zglGetShaderSourceFromFile(const std::string &fileName, GLsizei &length, GLchar* &source, bool preprocess);
		void zglLookupShaderSourceFromFile(const std::string &fileName, GLsizei &length, GLchar* &source, bool preprocess);

		GLsizei lookup_get_file_contents(const std::string &fileName, GLsizei &length, GLchar* &source);
		void zglPreprocessShaderSource(const std::string &currentPath, GLsizei &length, GLchar* &source);
		void zglPreprocessShaderSourceDirective(const std::string &currentPath, GLsizei start, GLsizei &length, GLchar* &source);
		void zglPreprocessShaderSourceIncludeDirective(const std::string &currentPath, GLsizei start, GLsizei stringStart, GLsizei &length, GLchar* &source);
		void zglPreprocessShaderSourceIncludeFile(const std::string &currentPath, GLsizei start, GLsizei end, const std::string &fileName, bool tryRelativePath, GLsizei &length, GLchar* &source);
		void zglPreprocessShaderSourceStreamReplace(GLsizei replacmentLength, const GLchar* replacment, GLsizei start, GLsizei end, GLsizei &length, GLchar* &source);


		void zglRegisterShaderSourceDirectory(const std::string &directory) {
			if (directory.empty())
				return;
			for (size_t i = 0; i < shaderDirectories.size(); i++)
				if (shaderDirectories[i] == directory)
					return;
			shaderDirectories.push_back(directory.back() == '/' || directory.back() == '\\' ? directory : directory + '/');
		}

		void zglRegisterShaderSourceDirectory(const GLchar* directory) {
			zglRegisterShaderSourceDirectory(std::string(directory));
		}

		void zglShaderSource(GLuint shader, const std::string &fileName, bool preprocess) {
			if (::glIsShader(shader)) {
				GLsizei length;
				GLchar* source;
				zglLookupShaderSourceFromFile(fileName, length, source, preprocess);
				const GLchar* source2 = source;
				glShaderSource(shader, 1, &source2, nullptr);
				zglDeallocate(source);
			}
		}

		void zglShaderSource(GLuint shader, const GLchar* fileName, bool preprocess) {
			zglShaderSource(shader, std::string(fileName), preprocess);
		}

		inline void zglGetShaderSourceFromFile(const std::string &fileName, GLsizei &length, GLchar* &source, bool preprocess) {
			length = 0;
			source = nullptr;
			if (fileName.empty())
				return;
			length = (GLsizei) utils::get_file_contents(fileName, source);
			if (preprocess)
				zglPreprocessShaderSource(utils::dirname(fileName), length, source);
		}

		inline void zglLookupShaderSourceFromFile(const std::string &fileName, GLsizei &length, GLchar* &source, bool preprocess) {
			length = 0;
			source = nullptr;
			if (fileName.empty())
				return;
			length = (GLsizei) utils::get_file_contents(fileName, source);
			if (preprocess)
				zglPreprocessShaderSource(utils::dirname(fileName), length, source);
			if (source == nullptr) {
				GLsizei pathIndex = lookup_get_file_contents(fileName, length, source);
				if (preprocess)
					zglPreprocessShaderSource(pathIndex >= 0 ? shaderDirectories[pathIndex] : utils::dirname(fileName), length, source);
			}
		}

		inline void zglPreprocessShaderSource(const std::string &currentPath, GLsizei &length, GLchar* &source) {
			for (GLsizei i = 0; i < length; i++)
				if (source[i] == '#')
					zglPreprocessShaderSourceDirective(currentPath, i, length, source);
		}

		inline GLsizei lookup_get_file_contents(const std::string &fileName, GLsizei &length, GLchar* &source) {
			GLsizei index;
			for (index = 0; (size_t)index < shaderDirectories.size(); index++) {
				length = (GLsizei) utils::get_file_contents(shaderDirectories[index] + fileName, source);
				if (source != nullptr)
					return index;
			}
			length = 0;
			source = nullptr;
			return -1;
		}

		inline void zglPreprocessShaderSourceDirective(const std::string &currentPath, GLsizei start, GLsizei &length, GLchar* &source) {
			GLsizei i = start + 1;
			while (i < length && source[i] == ' ') i++;
			if (i + 7 < length && std::string(source + i, 8) == "include ") {
				i += 7;
				while (i < length && source[i] == ' ') i++;
				zglPreprocessShaderSourceIncludeDirective(currentPath, start, i, length, source);
			}
		}

		inline void zglPreprocessShaderSourceIncludeDirective(const std::string &currentPath, GLsizei start, GLsizei stringStart, GLsizei &length, GLchar* &source) {
			if (stringStart + 1 < length) {
				GLchar encloser = source[stringStart];
				stringStart++;
				GLsizei i = stringStart;
				if (encloser == '<')
					encloser = '>';
				if (encloser == '"' || encloser == '>') {
					while (i < length && source[i] != encloser) i++;
					if (i < length) {
						std::string fileName = std::string(source + stringStart, i - stringStart);
						i++;
						zglPreprocessShaderSourceIncludeFile(currentPath, start, i, fileName, encloser == '"', length, source);
					}
				}
			}
		}

		inline void zglPreprocessShaderSourceIncludeFile(const std::string &currentPath, GLsizei start, GLsizei end, const std::string &fileName, bool tryRelativePath, GLsizei &length, GLchar* &source) {
			GLsizei includeLength = 0;
			GLchar* includeSource = nullptr;
			if (tryRelativePath)
				zglGetShaderSourceFromFile(currentPath + fileName, includeLength, includeSource, true);
			if (includeSource == nullptr)
				zglLookupShaderSourceFromFile(fileName, includeLength, includeSource, true);
			if (includeSource != nullptr) {
				zglPreprocessShaderSourceStreamReplace(includeLength, includeSource, start, end, length, source);
				zglDeallocate(includeSource);
			} else {
				zglPreprocessShaderSourceStreamReplace(-1, ("#error cannot find include file " + fileName + ".").c_str(), start, end, length, source);
			}
		}

		inline void zglPreprocessShaderSourceStreamReplace(GLsizei replacmentLength, const GLchar* replacment, GLsizei start, GLsizei end, GLsizei &length, GLchar* &source) {
			if (replacmentLength == -1) {
				replacmentLength = 0;
				while (replacment[replacmentLength] != 0) replacmentLength++;
			} else if (replacment[replacmentLength - 1] == 0)
				replacmentLength--;
			GLsizei newLength = length + replacmentLength - end + start;
			GLchar* newSource = (GLchar*)zglAllocate(newLength);
			memcpy(newSource, source, start);
			memcpy(newSource + start, replacment, replacmentLength);
			memcpy(newSource + start + replacmentLength, source + end, length - end);
			zglDeallocate(source);
			source = newSource;
			length = newLength;
		}

	} // namespace gl

} // namespace zen